package pathfinding

import (
	"fmt"
	"time"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/utils/serialize"
)

const (
	Init byte = iota
	Stop

	Path
	Plan
	Auto

	QuerySeat = 50
	QueryFix  = 51

	UpdateNode  = 120
	UpdateFixed = 121
	MoveSeat    = 122

	GetNode        = 124
	GetItem        = 125
	GetItemList    = 126
	GetFixedList   = 127
	CanAndPreMove  = 128
	GetLinksBy     = 129
	GetLinks       = 130
	AddLinks       = 131
	RemoveLinks    = 132
	HasView        = 133
	CanAndPreRange = 134
	RandomPosition = 135

	OnInit = 155
)

type Service struct {
}

func (service *Service) ID() string {
	return "pathfinding"
}
func (service *Service) Init(node cluster.Node) {
	var grid *Grid
	var zr *ZeroRange
	instance := NewHexInstance(RowHexMode)
	jps := NewJPS(instance)
	mode := 0
	node.On(Init, func(message asteroid.Message) {
		var data RegisterData
		slot := serialize.GetSlot()
		message.UnSerialize(slot)
		if err := slot.Set(&data); err != nil {
			fmt.Println("path", err)
			slot.Release()
			return
		}
		initNode := message.GetFrom()
		message.Response(true)
		zr = NewZeroRange(data.RangeGap, instance)
		startPosition := Vector2{
			X: data.Center.X - data.Size.X/2,
			Y: data.Center.Y - data.Size.Y/2,
		}
		maxX := data.Size.X
		maxY := data.Size.Y
		//fmt.Println("path", data.Center, data.Size, maxX, maxY)
		n := make([][]*DefineNodeContent, maxX)
		m := map[string]*SeatMember{}
		for i := int16(0); i < data.Size.X; i++ {
			n[i] = make([]*DefineNodeContent, maxY)
		}
		for index := range data.Nodes {
			node := &data.Nodes[index]
			//if node.Node != nil && node.Node.Fixed != nil {
			//	fmt.Println("node", node.Position, node.Node.Fixed.Item)
			//}
			startPosition.Tailor(&node.Position)
			//if node.Node != nil && node.Node.Fixed != nil {
			//	fmt.Println("node", node.Position, node.Node.Fixed.Item)
			//}

			if node.Position.X < maxX && node.Position.Y < maxY {
				n[node.Position.X][node.Position.Y] = &DefineNodeContent{}
				n[node.Position.X][node.Position.Y].InitFrom(mode, node.Node, m, &node.Position)
			}
		}
		for index, _ := range data.Boundary {
			position := data.Boundary[index]
			startPosition.Tailor(&position)
			if n[position.X][position.Y] == nil {
				n[position.X][position.Y] = &DefineNodeContent{}
			}
			n[position.X][position.Y].IsBoundary = true
		}
		grid = NewGrid(startPosition, n, m)
		if data.NodeCostList != nil {
			for _, nodeCost := range data.NodeCostList {
				grid.CostType[nodeCost.Type] = nodeCost.Cost
			}
		}

		zr.BindGrid(grid)
		zr.InitArea()
		slot.Release()
		node.Direct(initNode).Send(OnInit)
	})
	node.On(Stop, func(message asteroid.Message) {
		fmt.Println("close findpath", node.Token())
		_ = node.Close()
	})
	node.On(Path, func(message asteroid.Message) {
		// 2点之间移动
		pathData := PathDataPool.Get().(*PathData)
		message.UnSerialize(pathData)

		startPosition := DecodeVector2(pathData.Start)
		endPosition := DecodeVector2(pathData.End)
		grid.start.Tailor(startPosition)
		grid.start.Tailor(endPosition)

		var mask PathMask
		switch mode {
		case 0:
			mm := StrategyMaskPool.Get().(*StrategyMask)
			_, _ = mm.UnmarshalMsg(pathData.Data)
			mm.Init(grid.start, instance)
			_, startNode := grid.Node(startPosition)
			mm.BindStart(startPosition, startNode)
			_, endNode := grid.Node(endPosition)
			mm.BindEnd(endPosition, endNode)
			mask = mm
		}

		positionsResponse := PositionsResponsePool.Get().(*PositionsResponse)
		//fmt.Println(startPosition, endPosition, position)
		var ok bool
		ok, positionsResponse.result = jps.FindPath(grid, startPosition, endPosition, mask, pathData.Complete, positionsResponse.result)
		if ok {
			//fmt.Println("path", startPosition, endPosition, ok, result)
			grid.start.RestoreList(positionsResponse.result)
			positionsResponse.Positions = positionsResponse.result.Encode()
			message.Response(positionsResponse)
		} else {
			message.Response(nil)
		}
		mask.Release()
		pathData.Release()
		positionsResponse.Release()
	})
	node.On(Plan, func(message asteroid.Message) {
		//fmt.Println("plan")
		// 2点之间行动规划：分层地图节点
		planData := PlanDataPool.Get().(*PlanData)
		message.UnSerialize(planData)

		startPosition := DecodeVector2(planData.Start)
		endPosition := DecodeVector2(planData.End)
		grid.start.Tailor(startPosition)
		grid.start.Tailor(endPosition)

		var mask PathMask
		switch mode {
		case 0:
			mm := StrategyMaskPool.Get().(*StrategyMask)
			_, _ = mm.UnmarshalMsg(planData.Data)
			mm.Init(grid.start, instance)
			_, startNode := grid.Node(startPosition)
			mm.BindStart(startPosition, startNode)
			_, endNode := grid.Node(endPosition)
			mm.BindEnd(endPosition, endNode)
			mask = mm
		}

		//startTime := time.Now()
		//fmt.Println("plan", startTime)
		twoPositionsResponse := TwoPositionsResponsePool.Get().(*TwoPositionsResponse)
		var ok bool
		//fmt.Println("plan", start, end, startPosition, endPosition)
		ok, twoPositionsResponse.result1 = zr.Plan(*startPosition, *endPosition, mask, planData.Complete, twoPositionsResponse.result1)
		//middle := time.Now()
		//fmt.Println("plan1", ok, planResult)
		if ok {
			lastIndex := len(twoPositionsResponse.result1) - 1
			if !twoPositionsResponse.result1[lastIndex].Equals(endPosition) {
				twoPositionsResponse.result1[lastIndex] = *endPosition
			}
			//fmt.Println("plan", ok, twoPositionsResponse.result1)
			ok, twoPositionsResponse.result2 = jps.FindPath(grid, startPosition, &twoPositionsResponse.result1[1], mask, planData.Complete, twoPositionsResponse.result2)

			//fmt.Println("plan", ok, twoPositionsResponse.result2)
			// 需要调整最终坐标
			if mask != nil && mask.NeedChangeTarget() {
				if lastIndex >= 2 {
					vs := Vector2Slice{}
					_, vs = jps.FindPath(grid, &twoPositionsResponse.result1[lastIndex-1], &twoPositionsResponse.result1[lastIndex], mask, planData.Complete, vs)
					for i := len(vs) - 1; i >= 0; i-- {
						//fmt.Println("plan1", lastIndex, vs[i], mask.IsTargetPositions(&vs[i]), vs[0:i])
						if !mask.IsTargetPositions(&vs[i]) {
							twoPositionsResponse.result1[lastIndex] = vs[i]
							break
						}
					}
				} else {
					vs := twoPositionsResponse.result2
					//fmt.Println("plan", ok, twoPositionsResponse.result2)
					for i := len(vs) - 1; i >= 0; i-- {
						//fmt.Println("plan2", vs[i], mask.IsTargetPositions(&vs[i]), vs[0:i+1])
						if !mask.IsTargetPositions(&vs[i]) {
							twoPositionsResponse.result1[lastIndex] = vs[i]
							twoPositionsResponse.result2 = vs[0 : i+1]
							break
						}
					}
				}
			}

			//fmt.Println("plan", ok, start, end, startPosition, endPosition, planResult.Encode(), pathResult.Encode())
			grid.start.RestoreList(twoPositionsResponse.result1)
			grid.start.RestoreList(twoPositionsResponse.result2)
			//fmt.Println("plan", ok, start, end, startPosition, endPosition, planResult.Encode(), pathResult.Encode())
			//endTime := time.Now()
			//fmt.Println("plan2", endTime, endTime.Sub(startTime))
			twoPositionsResponse.Positions1 = twoPositionsResponse.result1.Encode()
			twoPositionsResponse.Positions2 = twoPositionsResponse.result2.Encode()
			message.Response(twoPositionsResponse)
		} else {
			//fmt.Println("plan", ok, start, end, planResult)
			message.Response(nil)
		}
		mask.Release()
		planData.Release()
		twoPositionsResponse.Release()
	})
	node.On(Auto, func(message asteroid.Message) {
		// 自动寻找周围空余点
		autoData := AutoDataPool.Get().(*AutoData)
		message.UnSerialize(autoData)

		start := DecodeVector2(autoData.Start)
		end := DecodeVector2(autoData.End)

		var request PathPreSeat
		switch mode {
		case 0:
			rr := StrategyBlockRequestPool.Get().(*StrategyBlockRequest)
			_, _ = rr.UnmarshalMsg(autoData.Data)
			request = rr
		}

		request.Init(grid, start, instance)
		grid.start.Tailor(start)
		grid.start.Tailor(end)

		positionsResponse := PositionsResponsePool.Get().(*PositionsResponse)
		var ok bool
		ok, positionsResponse.result = jps.FindEmpty(grid, start, end, autoData.Timestamp, request, positionsResponse.result)
		//fmt.Println(ok, result)
		if ok {
			grid.start.RestoreList(positionsResponse.result)
			positionsResponse.Positions = positionsResponse.result.Encode()
			message.Response(positionsResponse)
		} else {
			message.Response(nil)
		}
		request.Release()
		autoData.Release()
		positionsResponse.Release()
	})
	node.On(QuerySeat, func(message asteroid.Message) {
		bag := serialize.GetBag()
		message.UnSerialize(bag)
		var p []byte
		_ = bag.Read(0, &p)
		position := DecodeVector2(p)
		grid.start.Tailor(position)
		bag.Release()
		seat, _ := grid.GetSeatWithPosition(position, time.Now().UnixNano()/1e6)
		if seat != nil {
			message.Response(seat.Item)
		} else {
			message.Response([]byte{})
		}
	})
	node.On(QueryFix, func(message asteroid.Message) {
		bag := serialize.GetBag()
		message.UnSerialize(bag)
		var p []byte
		_ = bag.Read(0, &p)
		position := DecodeVector2(p)
		grid.start.Tailor(position)
		bag.Release()
		ok, node := grid.Node(position)
		if ok && node.GetFixed() != nil {
			message.Response(node.GetFixed().Item)
		} else {
			message.Response([]byte{})
		}
	})
	node.On(UpdateNode, func(message asteroid.Message) {
		updateNodeData := UpdateNodePool.Get().(*UpdateNodeData)
		message.UnSerialize(updateNodeData)
		p := DecodeVector2(updateNodeData.Position)
		grid.start.Tailor(p)
		grid.UpdateNode(p, updateNodeData.Identity, updateNodeData.T)
		message.Response(true)
		updateNodeData.Release()
	})
	node.On(UpdateFixed, func(message asteroid.Message) {
		updateFixedData := UpdateFixedPool.Get().(*UpdateFixedData)
		message.UnSerialize(updateFixedData)
		positions := DecodeVector2Slice(updateFixedData.Positions)
		grid.start.TailorList(positions)
		for _, pp := range positions {
			grid.UpdateFixed(&pp, updateFixedData.Identity, updateFixedData.T, updateFixedData.View, updateFixedData.Item, updateFixedData.Timestamp)
		}
		message.Response(true)
		updateFixedData.Release()
	})
	node.On(MoveSeat, func(message asteroid.Message) {
		moveSeatData := MoveSeatPool.Get().(*MoveSeatData)
		message.UnSerialize(moveSeatData)

		var startPosition *Vector2
		var endPosition *Vector2
		if moveSeatData.Start != nil && len(moveSeatData.Start) > 0 {
			startPosition = DecodeVector2(moveSeatData.Start)
			grid.start.Tailor(startPosition)
		}
		if moveSeatData.End != nil && len(moveSeatData.End) > 0 {
			endPosition = DecodeVector2(moveSeatData.End)
			grid.start.Tailor(endPosition)
		}
		var extend interface{}
		switch mode {
		case 0:
			rr := StrategyExtendPool.Get().(*StrategyExtend)
			_, _ = rr.UnmarshalMsg(moveSeatData.Extend)
			extend = rr
		}

		//fmt.Println("move seat", moveSeatData, startPosition, endPosition)
		message.Response(grid.UpdateSeat(moveSeatData.Item, startPosition, endPosition, moveSeatData.StartTime, moveSeatData.Identity, moveSeatData.View, extend))
		moveSeatData.Release()
	})
	node.On(GetNode, func(message asteroid.Message) {
		getNodeData := GetNodePool.Get().(*GetNodeData)
		message.UnSerialize(getNodeData)
		position := DecodeVector2(getNodeData.Position)
		grid.start.Tailor(position)
		_, node := grid.Node(position)
		if node != nil && node.Exist() {
			fixed := node.GetFixed()
			if fixed != nil {
				message.Response([]interface{}{fixed.Identity, fixed.Type, fixed.Item})
			} else {
				message.Response([]interface{}{node.GetIdentity(), node.GetType(), nil})
			}
		} else {
			message.Response([]interface{}{0, 0, nil})
		}
		getNodeData.Release()
	})
	node.On(GetItem, func(message asteroid.Message) {
		getItemData := GetItemPool.Get().(*GetItemData)
		message.UnSerialize(getItemData)
		position := DecodeVector2(getItemData.Position)
		grid.start.Tailor(position)
		t, s, i := grid.GetItem(*position, getItemData.Timestamp)
		message.Response([]interface{}{t, s, i})
		getItemData.Release()
	})
	node.On(GetItemList, func(message asteroid.Message) {
		getItemListData := GetItemListPool.Get().(*GetItemListData)
		message.UnSerialize(getItemListData)
		positions := DecodeVector2Slice(getItemListData.Positions)

		grid.start.TailorList(positions)
		tt, ss, ii := grid.GetItemList(positions, getItemListData.Timestamp)
		//fmt.Println("get item list", positions, tt, ss, ii)
		message.Response([]interface{}{tt, ss, ii})
		getItemListData.Release()
	})
	node.On(GetFixedList, func(message asteroid.Message) {
		getItemListData := GetItemListPool.Get().(*GetItemListData)
		message.UnSerialize(getItemListData)
		positions := DecodeVector2Slice(getItemListData.Positions)
		grid.start.TailorList(positions)
		ss, ii := grid.GetFixedList(positions)
		message.Response([]interface{}{ss, ii})
		getItemListData.Release()
	})
	node.On(CanAndPreMove, func(message asteroid.Message) {
		canAndPreMoveData := CanAndPreMovePool.Get().(*CanAndPreMoveData)
		message.UnSerialize(canAndPreMoveData)

		currentPosition := DecodeVector2(canAndPreMoveData.Start)
		targetPosition := DecodeVector2(canAndPreMoveData.End)

		grid.start.Tailor(currentPosition)
		grid.start.Tailor(targetPosition)

		var request PathPreSeat
		switch mode {
		case 0:
			rr := StrategyBlockRequestPool.Get().(*StrategyBlockRequest)
			if _, err := rr.UnmarshalMsg(canAndPreMoveData.Data); err != nil {
				fmt.Println("canAndPreMove", err)
			}
			request = rr
		}
		request.Init(grid, currentPosition, instance)
		response := grid.CanAndPreMove(currentPosition, targetPosition, canAndPreMoveData.Timestamp, request)
		if response != nil {
			b, _ := response.MarshalMsg(nil)
			message.Response(b)
		} else {
			message.Response(nil)
		}
		canAndPreMoveData.Release()
	})
	node.On(CanAndPreRange, func(message asteroid.Message) {
		canAndPreRangeData := CanAndPreRangePool.Get().(*CanAndPreRangeData)
		message.UnSerialize(canAndPreRangeData)

		currentPosition := DecodeVector2(canAndPreRangeData.Position)

		grid.start.Tailor(currentPosition)

		requests := make([]PathPreSeat, len(canAndPreRangeData.List))
		switch mode {
		case 0:
			for i, data := range canAndPreRangeData.List {
				rr := StrategyBlockRequestPool.Get().(*StrategyBlockRequest)
				if _, err := rr.UnmarshalMsg(data); err != nil {
					fmt.Println("canAndPreRangeData", data, err)
				}

				rr.Init(grid, currentPosition, instance)
				requests[i] = rr
			}

		}
		//fmt.Println("can and pre range start", requests)
		positions := [][]byte{}
		position := Vector2Pool.Get().(*Vector2)
		flag := false
		instance.RandomPosition(currentPosition, canAndPreRangeData.Distance, func(point *Vector2) bool {
			if grid.CanAndPreRange(point, canAndPreRangeData.Timestamp, requests[0]) {
				//ok, node := grid.n(point, requests[0].GetMask())
				//fmt.Println("range", ok, node.Type, node.Identity)
				position.X = point.X
				position.Y = point.Y
				grid.start.Restore(position)
				positions = append(positions, position.Encode())
				if len(positions) == len(requests) {
					flag = true
					return false
				}
				instance.RangePosition(point, canAndPreRangeData.Distance, func(point *Vector2) bool {
					if grid.CanAndPreRange(point, canAndPreRangeData.Timestamp, requests[len(positions)]) {
						//ok, node := grid.n(point, requests[len(positions)].GetMask())
						//fmt.Println("range", ok, node.Type, node.Identity)
						position.X = point.X
						position.Y = point.Y
						grid.start.Restore(position)
						positions = append(positions, position.Encode())
						if len(positions) == len(requests) {
							flag = true
							return false
						}
					}
					return true
				})
				return false
			}
			return true
		})
		//fmt.Println("can and pre range end")
		//fmt.Println("can random", DecodeVector2(canAndPreRangeData.Position), currentPosition, flag, position)
		if flag {
			message.Response(positions)
		} else {
			message.Response(nil)
		}
		Vector2Pool.Put(position)
		canAndPreRangeData.Release()
	})
	node.On(RandomPosition, func(message asteroid.Message) {
		randomPositionData := RandomPositionPool.Get().(*RandomPositionData)
		message.UnSerialize(randomPositionData)
		position := Vector2Pool.Get().(*Vector2)
		result := grid.RandomPosition(randomPositionData.CanType, randomPositionData.CanFix, randomPositionData.CanSeat, randomPositionData.Timestamp, position)
		if result {
			grid.start.Restore(position)
			message.Response(position.Encode())
		} else {
			message.Response(nil)
		}
		Vector2Pool.Put(position)
		randomPositionData.Release()
	})
	node.On(GetLinksBy, func(message asteroid.Message) {
		getLinksByData := GetLinksByPool.Get().(*GetLinksByData)
		message.UnSerialize(getLinksByData)

		position := DecodeVector2(getLinksByData.Position)
		grid.start.Tailor(position)
		//fmt.Println("get links by", position, relation)
		ss := grid.GetLinksBy(getLinksByData.Relation, *position)

		positionsResponse := PositionsResponsePool.Get().(*PositionsResponse)
		positionsResponse.Positions = ss
		message.Response(positionsResponse)
		getLinksByData.Release()
		positionsResponse.Release()
	})
	node.On(GetLinks, func(message asteroid.Message) {
		getLinksData := GetLinksByPool.Get().(*GetLinksData)
		message.UnSerialize(getLinksData)
		position := DecodeVector2(getLinksData.Position)
		grid.start.Tailor(position)
		//fmt.Println("get links", position)
		ss := grid.GetLinks(*position)
		b, _ := ss.MarshalMsg(nil)
		message.Response(b)
		getLinksData.Release()
	})
	node.On(AddLinks, func(message asteroid.Message) {
		addLinksData := AddLinksPool.Get().(*AddLinksData)
		message.UnSerialize(addLinksData)
		positions := DecodeVector2Slice(addLinksData.Positions)

		grid.start.TailorList(positions)
		//fmt.Println("add links", item, relation, positions)
		grid.AddLinks(addLinksData.Item, addLinksData.Relation, positions)
		message.Response(true)
		addLinksData.Release()
	})
	node.On(RemoveLinks, func(message asteroid.Message) {
		removeLinksData := RemoveLinksPool.Get().(*RemoveLinksData)
		message.UnSerialize(removeLinksData)
		positions := DecodeVector2Slice(removeLinksData.Positions)

		grid.start.TailorList(positions)
		//fmt.Println("remove links", item, relation, positions)
		grid.RemoveLinks(removeLinksData.Item, removeLinksData.Relation, positions)
		message.Response(true)
		removeLinksData.Release()
	})
	node.On(HasView, func(message asteroid.Message) {
		hasViewData := HasViewPool.Get().(*HasViewData)
		message.UnSerialize(hasViewData)
		startPosition := DecodeVector2(hasViewData.Start)
		targetPosition := DecodeVector2(hasViewData.Target)

		grid.start.Tailor(startPosition)
		grid.start.Tailor(targetPosition)
		ss := grid.HasView(startPosition, targetPosition, hasViewData.Identity, hasViewData.Distance, hasViewData.Self, hasViewData.Timestamp)
		message.Response(ss)
		hasViewData.Release()
	})
}
func (service *Service) InitClient(client *cluster.Client) {

}
func (service *Service) CloseInstance(node cluster.Node) {

}
func (service *Service) CloseClient(client *cluster.Client) {

}

func (service *Service) Master(node cluster.Node) {

}

func NewService() *Service {
	s := &Service{}
	return s
}
